﻿package com.adobe.crypto
{
	import com.hurlant.crypto.Crypto;
	import com.hurlant.crypto.hash.MD5;
	import com.hurlant.crypto.prng.Random;
	import com.hurlant.crypto.symmetric.ICipher;
	import com.hurlant.crypto.symmetric.IPad;
	import com.hurlant.crypto.symmetric.IVMode;
	import com.hurlant.util.Base64;
	import com.hurlant.util.Hex;
	
	import flash.crypto.generateRandomBytes;
	import flash.utils.ByteArray;
	
	/**
	 * AES 算法的加密解密工具类。
	 *
	 * @author Fuchun，modified by caichengyang
	 * @langversion 3.0
	 * @playerversion Flash 11.1
	 * @productversion 1.0
	 */
	public class AES
	{
		
		/**
		 * 默认的算法与模式名称。
		 */
		public static const DEFAULT_CIPHER_NAME:String = "aes-128-cbc";
		
		/**
		 * 默认的填充模式。<code>pkcs5</code>
		 */
		public static const DEFAULT_PADNAME:String = "pkcs5";
		
		/**
		 * 无填充。
		 */
		public static const NULL_PADDING:String = "null";
		
		private static const RAND:Random = new Random();
		
		public static function generateKey(name:String):ByteArray
		{
			var keyLength:uint = Crypto.getKeySize(name);
			var key:ByteArray = new ByteArray();
			RAND.nextBytes(key, keyLength);
			return key;
		}
		
		public static function generateIV(name:String, key:ByteArray):ByteArray
		{
			var cipher:ICipher = Crypto.getCipher(name, key);
			var iv:ByteArray = new ByteArray();
			RAND.nextBytes(iv, cipher.getBlockSize());
			return iv;
		}
		
		/**
		 * 生成密钥联合和向量，按照秘钥生成与之对应的IV
		 * @param secret 密钥，为String或ByteArray
		 * @param givenSalt 指定salt，为ByteArray，长度为8位
		 * @return 返回一个包含密钥和IV的Object
		 */
		public static function generateSaltedKeyIV(secret:*,givenSalt:ByteArray = null):Object
		{
			const keyLength:uint = 32;
			const ivLength:uint = 16;
			var totalLength:uint = keyLength + ivLength;
			var md5:com.hurlant.crypto.hash.MD5 = new com.hurlant.crypto.hash.MD5();
			
			var concatBa:Function = function (ba1, ba2):ByteArray
			{
				var ba3:ByteArray = new ByteArray();
				ba3.writeBytes(ba1);
				ba3.writeBytes(ba2);
				return ba3;
			}
			if(!givenSalt)
			{
				var salt:ByteArray = generateRandomBytes(8);
			}
			else if(givenSalt.length != 8)
			{
				throw new Error("salt length must be 8");
			}
			else
			{
				salt = givenSalt;
			}
			var concat:ByteArray = new ByteArray();
			
			if (secret is String)
			{
				concat.writeUTFBytes(secret);
			}
			else if (secret is ByteArray)
			{
				concat.writeBytes(secret);
			}
			else
			{
				throw new Error("secret must be String or ByteArray");
			}
			concat.writeBytes(salt);
			
			var digest:ByteArray = md5.hash(concat);
			var hash:ByteArray = new ByteArray();
			hash.writeBytes(digest);
			while (hash.length < totalLength)
			{
				digest = md5.hash(concatBa(digest, concat));
				hash = concatBa(hash, digest);
			}
			hash.length = totalLength;
			var keyBa:ByteArray = new ByteArray();
			var ivBa:ByteArray = new ByteArray();
			keyBa.writeBytes(hash, 0, keyLength);
			ivBa.writeBytes(hash, keyLength);
			return {
				salt: salt,
				key : keyBa,
				iv  : ivBa
			};
		}
		
		public function AES(key:ByteArray, iv:ByteArray = null,
		                    name:String = DEFAULT_CIPHER_NAME,
		                    padName:String = DEFAULT_PADNAME)
		{
			_name = name;
			_key = key;
			_iv = iv;
			_padName = padName;
			init();
		}
		
		private var _name:String;
		// 密钥
		private var _key:ByteArray;
		// 填充模式
		private var _padName:String;
		private var _enc:ICipher;
		private var _dec:ICipher;
		
		// 向量
		private var _iv:ByteArray;
		
		public function get iv():ByteArray
		{
			return _iv;
		}
		
		public function set iv(value:ByteArray):void
		{
			_iv = value;
		}
		
		/**
		 * salt加密，便于对接jsCrypto的AES加密
		 * @param input 需要加密的输入，只支持字符串或ByteArray
		 * @param secret 密码，只支持字符串或ByteArray
		 * @param returnType 返回结果的类型，可选值为<code>base64</code>或<code>hex</code>
		 * @return 返回的加密结果
		 */
		public static function saltEncrypt(input:*, secret:*, returnType:String = "base64"):String
		{
			var inputBytes:ByteArray = new ByteArray();
			
			if (input is String)
			{
				inputBytes.writeUTFBytes(input as String);
			}
			else if (input is ByteArray)
			{
				inputBytes.writeBytes(input as ByteArray, 0, input.length);
			}
			else
			{
				throw (new Error("input must be String or ByteArray"));
			}
			
			var data:Object = generateSaltedKeyIV(secret);
			var aes:AES = new AES(data["key"],
					data["iv"],
					"aes-256-cbc");
			var output:ByteArray = new ByteArray();
			output.writeUTFBytes("Salted__");
			output.writeBytes(data["salt"]);
			output.writeBytes(aes.encrypt(inputBytes));
			
			switch (returnType)
			{
				case "hex":
					return Hex.fromArray(output);
				case "base64":
				default:
					return Base64.encodeByteArray(output);
			}
		}
		
		public static function saltDecrypt(input:String,secret:String,inputType:String = "base64"):String
		{
			var inputBytes:ByteArray = new ByteArray();
			var secretBytes:ByteArray = new ByteArray();
			switch (inputType)
			{
				case "hex":
					inputBytes = Hex.toArray(input);
					break;
				case "base64":
				default:
					inputBytes = Base64.decodeToByteArray(input);
			}
			inputBytes.position = 0;
			if (inputBytes.readUTFBytes(8) != "Salted__")
			{
				throw (new Error("cipher is not salted"));
			}
			var salt:ByteArray = new ByteArray();
			var cipher:ByteArray = new ByteArray();
			salt.writeBytes(inputBytes, 8, 8);
			cipher.writeBytes(inputBytes, 16);
			var data:Object = generateSaltedKeyIV(secret, salt);
			var aes:AES = new AES(data["key"], data["iv"], "aes-256-cbc");
			var tmpBa:ByteArray = aes.decrypt(cipher);
			tmpBa.position = 0;
			return tmpBa.readUTFBytes(tmpBa.length);
		}
		
		
		// base functions
		public function encrypt(input:ByteArray):ByteArray
		{
			var src:ByteArray = new ByteArray();
			var result:ByteArray = new ByteArray();
			src.writeBytes(input, 0, input.length);//备份输入内容，加密后不改变输入的ByteArray
			
			_enc.encrypt(input);
			result.writeBytes(input, 0, input.length);
			input.length = 0;
			input.writeBytes(src, 0, src.length);
			
			src.clear();
			return result;
		}
		
		public function decrypt(input:ByteArray):ByteArray
		{
			var src:ByteArray = new ByteArray();
			var result:ByteArray = new ByteArray();
			src.writeBytes(input, 0, input.length);
			
			_dec.decrypt(input);
			result.writeBytes(input, 0, input.length);
			input.length = 0;
			input.writeBytes(src, 0, src.length);
			
			src.clear();
			return result;
		}
		
		// encrypt string
		public function encryptString(input:String):ByteArray
		{
			if (!input || !input.length)
			{
				return null;
			}
			var inputBytes:ByteArray = new ByteArray();
			inputBytes.writeUTFBytes(input);
			return encrypt(inputBytes);
		}
		
		public function encryptString2Hex(input:String):String
		{
			var result:ByteArray = encryptString(input);
			return Hex.fromArray(result);
		}
		
		public function encryptString2Base64(input:String):String
		{
			var result:ByteArray = encryptString(input);
			return Base64.encodeByteArray(result);
		}
		
		// decrypt string
		public function decryptString(input:ByteArray):String
		{
			var decryptBytes:ByteArray = decrypt(input);
			decryptBytes.position = 0;
			var result:String = decryptBytes.readUTFBytes(decryptBytes.length);
			return result;
		}
		
		public function decryptStringFromHex(input:String):String
		{
			var inputBytes:ByteArray = Hex.toArray(input);
			var result:String = decryptString(inputBytes);
			return result;
		}
		
		public function decryptStringFromBase64(input:String):String
		{
			var inputBytes:ByteArray = Base64.decodeToByteArray(input);
			var result:String = decryptString(inputBytes);
			return result;
		}
		
		private function init():void
		{
			var _pad:IPad = Crypto.getPad(_padName);
			_enc = Crypto.getCipher(_name, _key, _pad);
			_dec = Crypto.getCipher(_name, _key, _pad);
			if (iv)
			{
				if (_enc is IVMode)
				{
					var encIvm:IVMode = _enc as IVMode;
					encIvm.IV = iv;
				}
				if (_dec is IVMode)
				{
					var decIvm:IVMode = _dec as IVMode;
					decIvm.IV = iv;
				}
			}
		}
	}
}

/*                var key : ByteArray = Hex.toArray("f056180ec970b169980f108c13305642"); //AES.generateKey(AES.DEFAULT_CIPHER_NAME);
                    var iv : ByteArray = Hex.toArray("912467427aa54cccf443d2ae206a63ce"); //AES.generateIV(AES.DEFAULT_CIPHER_NAME, key);
                    

                    
                   var aes : AES = new AES(key, iv, "aes-128-cbc", "null");
                   
                   var plainText:String = "caichengyang";
                   
                   var sString = aes.encryptString2Base64(plainText);
                   
                   var sString = aes.decryptStringFromBase64(s1);
                   
                   lblText.text = lblText.text + "加密后：" + s1 + "\r\n" + "解密后：" + s2 + "\r\n\r\n";*/